package com.boot2.core.dao;

import com.boot2.core.constant.CoreConstant;
import com.boot2.core.dao.exception.DAOException;
import com.boot2.core.dao.jpa.Criteria;
import com.boot2.core.dao.jpa.Criterion;
import com.boot2.core.dao.jpa.JpaDynamicSearchHolder;
import com.boot2.core.dao.jpa.JpaRestrictions;
import com.boot2.core.dao.mongo.MongoRestrictions;
import com.boot2.core.dict.PersistentEnum;
import com.boot2.core.utils.PrivateMethodUtil;
import com.boot2.core.utils.ReflectUtil;
import com.boot2.core.utils.StringUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.util.Assert;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author zhangweilin
 * @description:DAO工具类,此工具目前包括jpa和mongo
 * @date 2018/7/18
 */
public class DaoUtil {
    /**
     * 构建查询符号,来源为形如 appDeviceSn:like,appCode:like 的字符串
     *
     * @return
     */
    public static Map<String, String> getOpMap() {
//        String opStr = (String)(RequestContext.getRequest()==null?null:RequestContext.getRequest().getAttribute(CoreConstant.searchOpRequestAttibuteKey));
        String opStr = JpaDynamicSearchHolder.getAndClear();
//        Assert.isTrue(StringUtils.isNotEmpty(opStr), "操作符不能为空");
        Map<String, String> opMap = new HashMap<>();
        // String opStr = "appDeviceSn:like,appCode:like,";
        if (StringUtils.isNotEmpty(opStr)) {
            String[] kvArr = opStr.split(",");
            for (int i = 0; i < kvArr.length; i++) {
                String str = kvArr[i];
                String[] kvArr2 = str.split(":");
                // System.out.println("kvArr2: " + ArrayUtils.toString(kvArr2));
                Assert.isTrue(kvArr2.length == 2, "操作符【" + opStr + "】语法错误 ");
                opMap.put(kvArr2[0], kvArr2[1].toLowerCase());
            }
        }
        return opMap;
    }

    /**
     * 动态构建查询条件
     *
     * @param persistentEnum 持久层类型
     * @param paramMap
     * @param <T>
     * @return
     */
    public static <T> Object buildCriteria(PersistentEnum persistentEnum, Map<String, Object> paramMap) {
        Assert.notNull(paramMap, "参数map不能为null");
        // 临时存放between条件
        Map<String, Map<String, Object>> betweenMap = new HashMap<>();
        // 将between字段
        Set<String> betweenSet = new HashSet<>();
        Map<String, String> opMap = DaoUtil.getOpMap();
        //jpa专用
        Criteria<T> criteria = null;
        //mongodb专用
        Query query = null;
        //使用jpa的Criteria
        if (PersistentEnum.jpa.equals(persistentEnum)) {
            criteria = new Criteria<>();
            //使用mongo的query
        } else if (PersistentEnum.mongo.equals(persistentEnum)) {
            query = new Query();
        }
        Set<Map.Entry<String, Object>> entrySet = paramMap.entrySet();
        for (Map.Entry<String, Object> entry : entrySet) {
            String fieldName = entry.getKey();
            Object value = entry.getValue();
            if (CoreConstant.op.equalsIgnoreCase(fieldName) || "pageNo".equalsIgnoreCase(fieldName) || "pageSize".equalsIgnoreCase(fieldName)) {
                continue;
            }

            // 如果是区间查询条件，都以1和2结尾，凡是遇到这个的字段，将所有以1和2结尾的，放入betweenMap中，如接收到createTime1,createTime2，则对应结构会为
            // createTime:{createTime1:value1,createTime2:value2},也有可能是非between结构，如接收到
            // price1:则对应结构会为:price:{price1:value},也有可能接到多个，但是也并非between结构，如接收到price1,price2,price3，即三种价格，那么此时结构应该会为price:{price1:value1,price2:value2,price3:value3}.
            // 那么在组装between语句时，先通过between操作符，找到对应的between字符串字段，然后通过此字段，在betweenMap中找到对应的这个map，遍历完后，立刻从betweenMap中移除，等全部的between字段组装完毕后，如果betweenMap还有值，此时剩下的即为非between字段，此时
            // 继续遍历
            if (fieldName.endsWith("1") || fieldName.endsWith("2")) {
                String key0 = fieldName.substring(0, fieldName.length() - 1);
//                System.out.println("key0: " + key0);
                Map<String, Object> map0 = betweenMap.get(key0);
                if (map0 == null) {
                    map0 = new HashMap<>();
                }
                map0.put(fieldName, value);
                betweenMap.put(key0, map0);

                String op = opMap.get(key0);
                op = StringUtils.isEmpty(op) ? "=" : op;
                op = JpaRestrictions.opMap.get(op); // 将从页面获取到的操作符，转换为对应的调用方法
                op = StringUtils.isEmpty(op) ? "eq" : op;
                if ("between".equals(op)) {
                    betweenSet.add(key0);
                }
            } else {
                String op = opMap.get(fieldName);
                op = StringUtils.isEmpty(op) ? "=" : op;
                op = JpaRestrictions.opMap.get(op); // 将从页面获取到的操作符，转换为对应的调用方法
                op = StringUtils.isEmpty(op) ? "eq" : op;
                if ("between".equals(op)) {
                    betweenSet.add(fieldName);
                } else {
                    //使用jpa的Criteria
                    if (PersistentEnum.jpa.equals(persistentEnum)) {
                        addCriteria(criteria, fieldName, value, op);
                        //使用mongo的query
                    } else if (PersistentEnum.mongo.equals(persistentEnum)) {
                        addQuery(query, fieldName, value, op);
                    }
                }
            }
        }

        // 遍历 between字段
        Iterator<String> iterator = betweenSet.iterator();
        for (; iterator.hasNext(); ) {
            String fieldName = iterator.next();
            Map<String, Object> map = betweenMap.get(fieldName);
            if (map != null && map.size() == 2) {
                Object value1 = map.get(fieldName + "1");
                Object value2 = map.get(fieldName + "2");
                if (null != value1 && null != value2) {
                    String op = opMap.get(fieldName);
                    op = StringUtils.isEmpty(op) ? "=" : op;
                    op = JpaRestrictions.opMap.get(op); // 将从页面获取到的操作符，转换为对应的调用方法
                    op = StringUtils.isEmpty(op) ? "eq" : op;
                    Object[] valueArr = new Object[]{value1, value2};
                    //使用jpa的Criteria
                    if (PersistentEnum.jpa.equals(persistentEnum)) {
                        addCriteria(criteria, fieldName, valueArr, op);
                        //使用mongo的query
                    } else if (PersistentEnum.mongo.equals(persistentEnum)) {
                        addQuery(query, fieldName, valueArr, op);
                    }
                }
            }
            // 移除between字段
            betweenMap.remove(fieldName);
        }

        // 遍历移除between后剩下 的
        Set<Map.Entry<String, Map<String, Object>>> entrySet2 = betweenMap.entrySet();
        for (Map.Entry<String, Map<String, Object>> entry : entrySet2) {
            Map<String, Object> map = entry.getValue();
            Set<Map.Entry<String, Object>> entrySet3 = map.entrySet();
            for (Map.Entry<String, Object> entry2 : entrySet3) {
                String fieldName = entry2.getKey();
                Object value = entry2.getValue();
                String op = opMap.get(fieldName);
                op = StringUtils.isEmpty(op) ? "=" : op;
                op = JpaRestrictions.opMap.get(op); // 将从页面获取到的操作符，转换为对应的调用方法
                op = StringUtils.isEmpty(op) ? "eq" : op;
                //使用jpa的Criteria
                if (PersistentEnum.jpa.equals(persistentEnum)) {
                    addCriteria(criteria, fieldName, value, op);
                    //使用mongo的query
                } else if (PersistentEnum.mongo.equals(persistentEnum)) {
                    addQuery(query, fieldName, value, op);
                }
            }
        }
        //使用jpa的Criteria
        if (PersistentEnum.jpa.equals(persistentEnum)) {
            return criteria;
            //使用mongo的query
        } else if (PersistentEnum.mongo.equals(persistentEnum)) {
            return query;
        }
        return null;
    }


    /**
     * 动态构建查询条件
     * @param persistentEnum  持久层类型
     * @param t
     * @param <T>
     * @return
     */
    public static <T> Object buildCriteria(PersistentEnum persistentEnum, T t) {
        // String opStr = "appDeviceSn:like,appCode:like,id:<=,createTime:between";//
        // 从request里取
        Map<String, String> opMap = DaoUtil.getOpMap();
        //jpa专用
        Criteria<T> criteria = null;
        //mongodb专用
        Query query = null;
        //使用jpa的Criteria
        if (PersistentEnum.jpa.equals(persistentEnum)) {
            criteria = new Criteria<>();
            //使用mongo的query
        } else if (PersistentEnum.mongo.equals(persistentEnum)) {
            query = new Query();
        }
        List<Field> fieldList = ReflectUtil.getAllFieldWithParentNotTransientList(t.getClass(), "isDeleted");
        for (int i = 0; i < fieldList.size(); i++) {
            Field field = fieldList.get(i);
            try {
                String fieldName = field.getName();
//                System.out.println("fieldName = " + fieldName);
                //排除 $jacocoData 的情况
                if (fieldName.startsWith("$")) {
                    continue;
                }
                String methodName = "get" + StringUtils.changFirstWord(fieldName, StringUtils.toUpperCase);
//                System.out.println("methodName = " + methodName);
                Object value = PrivateMethodUtil.invokeMethod(t, methodName);
//                Object value = MethodUtils.invokeExactMethod(t, methodName);
                String op = opMap.get(fieldName);
                op = StringUtils.isEmpty(op) ? "=" : op;
                op = JpaRestrictions.opMap.get(op); // 将从页面获取到的操作符，转换为对应的调用方法
                op = StringUtils.isEmpty(op) ? "eq" : op;
                if ("between".equals(op)) {
                    Object value1 = MethodUtils.invokeExactMethod(t, "get" + StringUtils.changFirstWord(fieldName + "1", StringUtils.toUpperCase));
                    Object value2 = MethodUtils.invokeExactMethod(t, "get" + StringUtils.changFirstWord(fieldName + "2", StringUtils.toUpperCase));
                    value = new Object[]{value1, value2};
                }
                //使用jpa的Criteria
                if (PersistentEnum.jpa.equals(persistentEnum)) {
                    addCriteria(criteria, fieldName, value, op);
                    //使用mongo的query
                } else if (PersistentEnum.mongo.equals(persistentEnum)) {
                    addQuery(query, fieldName, value, op);
                }
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e1) {
                throw new DAOException("构建查找条件失败", e1);
            }
        }
        //使用jpa的Criteria
        if (PersistentEnum.jpa.equals(persistentEnum)) {
            return criteria;
            //使用mongo的query
        } else if (PersistentEnum.mongo.equals(persistentEnum)) {
            return query;
        }
        return null;
    }

    /**
     * 添加mongo query
     *
     * @param query
     * @param fieldName
     * @param value
     * @param op
     */
    public static void addQuery(Query query, String fieldName, Object value, String op) {
        if (null == value) {
            return;
        }
        org.springframework.data.mongodb.core.query.Criteria criteria = new org.springframework.data.mongodb.core.query.Criteria(fieldName);
        try {
            org.springframework.data.mongodb.core.query.Criteria criteria0 = (org.springframework.data.mongodb.core.query.Criteria) MethodUtils.invokeStaticMethod(MongoRestrictions.class, op, fieldName, value);
            if (null!=criteria0) {
                query.addCriteria(criteria0);
            }
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
            throw new DAOException("构建查找条件失败", e);
        }
    }

    /**
     * 添加jpa criteria
     *
     * @param criteria
     * @param fieldName
     * @param value
     * @param op
     * @param <T>
     */
    public static <T> void addCriteria(Criteria<T> criteria, String fieldName, Object value, String op) {
        if (null == value) {
            return;
        }
        Criterion criterion;
        try {
            criterion = (Criterion) MethodUtils.invokeStaticMethod(JpaRestrictions.class, op, fieldName, value, true);
            criteria.add(criterion);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
            throw new DAOException("构建查找条件失败", e);
        }
    }
}
